// // Copyright (c) 2020 Presto Labs Pte. Ltd.
// // Author: qiang

#include "coin2/exchange/bitget_futures/api_order/native_private_client.h"

#include <list>
#include <memory>
#include <string>

#include "coin2/exchange/base/api_util/url_params_builder.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/bitget_futures/symbology/product.h"

using AsioHttpContextList = std::list<std::shared_ptr<AsioHttpContext>>;
using coin2::exchange::bitget_base::api_order::BitgetOrderSubTopicId;
using RestParams = coin2::exchange::order::BitgetFuturesRestParams;

#define CHECK_API_LIMIT(action_type, query_type)                                               \
  if (local_rate_tracker_ &&                                                                   \
      (!local_rate_tracker_->HasEnoughRateLimit(ts, action_type, query_type))) {               \
    LOG_EVERY_N(WARNING, 50) << "Hit rate limit or api temporary disabled until " << __func__; \
    return {};                                                                                 \
  }

namespace {
using ActionType = bitget::api_util::BitgetRateTracker::ActionType;
using coin2::exchange::bitget::api_util::BitgetRateTracker;
using QueryType = coin2::exchange::bitget::api_util::QueryType;
}  // namespace

std::shared_ptr<AsioHttpContext> BitgetFuturesNativePrivateClient::QueryBalance(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();
  UrlParamsBuilder builder;
  if (!params.has_product_type()) {
    return {};
  }
  if (!is_first_query_account_balance_) {
    CHECK_API_LIMIT(ActionType::QUERY, QueryType::ACCOUNT);
  }
  is_first_query_account_balance_ = false;
  builder.PutGet("productType", params.product_type());
  auto* context =
      GetAuth()->CreateRequestByGetWithSignature("/api/mix/v1/account/accounts", &builder);
  if (local_rate_tracker_) {
    local_rate_tracker_->Push(1, ts, ActionType::QUERY, QueryType::ACCOUNT);
  }
  context->query_type = OrderSubTopicId::REST_ACCOUNT_BALANCE;
  context->extra_info = params.product_type();
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BitgetFuturesNativePrivateClient::QueryAccountPosition(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();
  UrlParamsBuilder builder;
  if (!params.has_product_type()) {
    return {};
  }

  if (!is_first_query_postion_balance_) {
    CHECK_API_LIMIT(ActionType::QUERY, QueryType::POSITION);
  }
  is_first_query_postion_balance_ = false;

  builder.PutGet("productType", params.product_type());
  auto* context =
      GetAuth()->CreateRequestByGetWithSignature("/api/mix/v1/position/allPosition", &builder);
  if (local_rate_tracker_) {
    local_rate_tracker_->Push(1, ts, ActionType::QUERY, QueryType::POSITION);
  }
  context->query_type = OrderSubTopicId::REST_ACCOUNT_POSITION;
  context->extra_info = params.product_type();
  return std::shared_ptr<AsioHttpContext>(context);
}

BitgetFuturesNativePrivateClient::BitgetFuturesNativePrivateClient(
    const MarketExchangeApi& mea,
    const NativeClientConfig& config_override) {
  mea_ = mea;
  NativeClientConfig config;
  config.set_rest_host("api.bitget.com");
  config.set_rest_host_port(443);
  config.MergeFrom(config_override);
  LOG(INFO) << "[BitgetFuturesNativePrivateClient] config: " << config.DebugString();
  auths_.Construct<BitgetRestAuth, NativeClientConfig>(config);
  rate_tracker_ = std::make_unique<BitgetRateTracker>(mea_, config);
  local_rate_tracker_ = static_cast<BitgetRateTracker*>(rate_tracker_.get());
}

std::shared_ptr<AsioHttpContext> BitgetFuturesNativePrivateClient::PlaceOrder(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();
  CHECK_API_LIMIT(ActionType::PLACE, QueryType::PLACE_ORDER);
  if (local_rate_tracker_) {
    local_rate_tracker_->Push(1, ts, ActionType::PLACE, QueryType::PLACE_ORDER);
  }
  UrlParamsBuilder builder;
  builder.PutPost("symbol", params.symbol());
  builder.PutPost("marginCoin", params.margin_coin());
  builder.PutPost("size", params.size());
  builder.PutPost("price", params.price());
  builder.PutPost("side", params.side());
  builder.PutPost("orderType", params.order_type());
  builder.PutPost("timeInForceValue", params.time_in_force());
  builder.PutPost("clientOid", params.client_order_id());
  auto* context =
      GetAuth()->CreateRequestByPostWithSignature("/api/mix/v1/order/placeOrder", &builder);
  context->query_type = OrderSubTopicId::SUBMIT_RESPONSE;
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BitgetFuturesNativePrivateClient::CancelOrder(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();
  CHECK_API_LIMIT(ActionType::CANCEL, QueryType::CANCEL_ORDER);
  if (local_rate_tracker_) {
    local_rate_tracker_->Push(1, ts, ActionType::CANCEL, QueryType::CANCEL_ORDER);
  }

  UrlParamsBuilder builder;
  builder.PutPost("symbol", params.symbol());
  builder.PutPost("marginCoin", params.margin_coin());
  builder.PutPost("orderId", params.order_id());

  auto* context =
      GetAuth()->CreateRequestByPostWithSignature("/api/mix/v1/order/cancel-order", &builder);
  context->query_type = OrderSubTopicId::CANCEL_RESPONSE;
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BitgetFuturesNativePrivateClient::QueryFills(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();
  CHECK_API_LIMIT(ActionType::QUERY, QueryType::FILL_ORDER);
  if (local_rate_tracker_) {
    local_rate_tracker_->Push(1, ts, ActionType::QUERY, QueryType::FILL_ORDER);
  }

  UrlParamsBuilder builder;
  builder.PutGet("productType", params.product_type());
  builder.PutGet("startTime", params.start_time());
  builder.PutGet("endTime", params.end_time());
  auto* context =
      GetAuth()->CreateRequestByGetWithSignature("/api/mix/v1/order/allFills", &builder);
  context->query_type = OrderSubTopicId::REST_FILL_ORDERS;
  return std::shared_ptr<AsioHttpContext>(context);
}

std::shared_ptr<AsioHttpContext> BitgetFuturesNativePrivateClient::PlaceOrderNorm(
    const OrderExecution& order_exe) {
  const auto& order_spec = *order_exe.order_spec;
  const auto proc_order_id = order_exe.proc_order_id;
  auto params = GetRestOrderParams(order_spec, proc_order_id);
  return PlaceOrder(params);
}

std::shared_ptr<AsioHttpContext> BitgetFuturesNativePrivateClient::CancelOrderNorm(
    const OrderExecution& order_exe) {
  auto params = GetRestCancelParams(*order_exe.oc);
  return CancelOrder(params);
}

std::shared_ptr<AsioHttpContext> BitgetFuturesNativePrivateClient::QueryOpenOrders(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();
  UrlParamsBuilder builder;
  if (!params.has_product_type()) {
    return {};
  }

  if (params.has_margin_coin()) {
    builder.PutGet("marginCoin", params.margin_coin());
  }
  CHECK_API_LIMIT(ActionType::QUERY, QueryType::OPEN_ORDER);
  if (local_rate_tracker_) {
    local_rate_tracker_->Push(1, ts, ActionType::QUERY, QueryType::OPEN_ORDER);
  }
  builder.PutGet("productType", params.product_type());
  auto* context =
      GetAuth()->CreateRequestByGetWithSignature("/api/mix/v1/order/marginCoinCurrent", &builder);
  context->query_type = OrderSubTopicId::REST_OPEN_ORDERS;
  return std::shared_ptr<AsioHttpContext>(context);
}

RestParams BitgetFuturesNativePrivateClient::GetRestOrderParams(
    const OrderSpec& order_spec,
    int64_t proc_order_id) {
  auto params = RestParams();
  using coin2::exchange::bitget_futures::symbology::BitgetFuturesProduct;
  auto product = BitgetFuturesProduct::FromStr(order_spec.product, order_spec.order_created_time);
  auto product_holder = GetProductHolder(product, order_spec.order_created_time);
  auto formatter = product_holder->product_info().order_formatter();
  params.set_price(formatter.FormatPrice(order_spec.price));
  params.set_size(formatter.FormatQty(order_spec.qty));
  params.set_symbol(order_spec.product);
  params.set_client_order_id(std::to_string(proc_order_id));

  params.set_margin_coin(product_holder->product().quote().native_currency());
  if (order_spec.order_side == OrderSide::BUY_OPEN_ORDER ||
      order_spec.order_side == OrderSide::BUY_ORDER) {
    params.set_side("open_long");
  } else if (order_spec.order_side == OrderSide::BUY_CLOSE_ORDER) {
    params.set_side("close_short");
  } else if (
      order_spec.order_side == OrderSide::SELL_OPEN_ORDER ||
      order_spec.order_side == OrderSide::SELL_ORDER) {
    params.set_side("open_short");
  } else if (order_spec.order_side == OrderSide::SELL_CLOSE_ORDER) {
    params.set_side("close_long");
  } else {
    NOTREACHED() << "Unknown order side: " << static_cast<int>(order_spec.order_side);
  }

  if (order_spec.order_type == OrderType::MARKET_ORDER) {
    params.set_order_type("market");
  } else if (order_spec.order_type == OrderType::LIMIT_ORDER) {
    params.set_order_type("limit");
  } else {
    NOTREACHED() << "Bitget exchange not support order side: "
                 << static_cast<int>(order_spec.order_side);
  }

  if (order_spec.duration == coin::proto::OrderDuration::FOK_ORDER) {
    params.set_time_in_force("fok");
  } else if (order_spec.duration == coin::proto::OrderDuration::IOC_ORDER) {
    params.set_time_in_force("ioc");
  } else if (order_spec.duration == coin::proto::OrderDuration::GTX_ORDER || order_spec.post_only) {
    params.set_time_in_force("post_only");
  } else {
    params.set_time_in_force("normal");
  }

  return params;
}

std::shared_ptr<AsioHttpContext> BitgetFuturesNativePrivateClient::QueryAllOrders(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();
  CHECK_API_LIMIT(ActionType::QUERY, QueryType::ALL_ORDER);
  if (local_rate_tracker_) {
    local_rate_tracker_->Push(1, ts, ActionType::QUERY, QueryType::ALL_ORDER);
  }

  UrlParamsBuilder builder;
  builder.PutGet("productType", params.product_type());
  builder.PutGet("startTime", params.start_time());
  builder.PutGet("endTime", params.end_time());
  builder.PutGet("pageSize", 500);
  auto* context =
      GetAuth()->CreateRequestByGetWithSignature("/api/mix/v1/order/historyProductType", &builder);
  context->query_type = OrderSubTopicId::REST_ALL_ORDERS;
  return std::shared_ptr<AsioHttpContext>(context);
}

RestParams BitgetFuturesNativePrivateClient::GetRestCancelParams(
    const IOrderContext& order_context) {
  auto params = RestParams();
  using coin2::exchange::bitget_futures::symbology::BitgetFuturesProduct;
  const std::string& product_str = order_context.order_spec().product;
  params.set_symbol(product_str);
  params.set_order_id(order_context.exchange_order_id());
  auto product =
      BitgetFuturesProduct::FromStr(product_str, order_context.order_spec().order_created_time);
  auto product_holder = GetProductHolder(product, order_context.order_spec().order_created_time);
  params.set_margin_coin(product_holder->product().quote().native_currency());

  return params;
}

AsioHttpContextList BitgetFuturesNativePrivateClient::SubmitRestQueryList(
    const RestQueryProto& query) {
  AsioHttpContextList list;
  switch (query.type()) {
    case RestQueryProto::QUERY_TRANSFERS: {
      break;
    }
    default: {
      return list;
    }
  }

  return list;
}